#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/bootmem.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/platform_device.h>

#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/cacheflush.h>

#include "gpufb.h"

#define GPU_IRQ na_graphic_0_irq
//#define CONFIG_GPUFB_DEBUG

#ifdef CONFIG_GPUFB_DEBUG
#define dprintk(msg...)	printk("%s: " msg, __func__)
#else
#define dprintk(msg...)
#endif

#ifdef CONFIG_GPUFB_DEBUG2
#define dprintk2(msg...) printk("%s: " msg, __func__)
#else
#define dprintk2(msg...)
#endif

static struct semaphore glt_sem;
static DECLARE_WAIT_QUEUE_HEAD(wq);
struct gpufb_par *par;
unsigned long fb_base0, fb_base1, vi_fb_base;
unsigned char gpu_free = 0;

static u32 reg_readl(u32 reg_base)
{
	dprintk2("Reading register at address %x, value = %x\n", (u32)reg_base, (u32)readl(reg_base));
	return readl(reg_base);
}

static void reg_writel(u32 reg_base, u32 val)
{
	dprintk2("Writing register at address %x, value = %x\n", (u32)reg_base, (u32)val);
	writel(val, reg_base);
	dprintk2("Verifying: value = %x\n", readl(reg_base));
}

static int gpufb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	//struct gpufb_par *fbi = info->par;

	dprintk("check_var(var=%p, info=%p)\n", var, info);

	if (var->bits_per_pixel == 16) {
		var->red.offset		= 11;
		var->green.offset	= 5;
		var->blue.offset	= 0;
		var->red.length		= 5;
		var->green.length	= 6;
		var->blue.length	= 5;
		var->transp.length	= 0;
	} else {
		var->red.length		= var->bits_per_pixel;
		var->red.offset		= 0;
		var->green.length	= var->bits_per_pixel;
		var->green.offset	= 0;
		var->blue.length	= var->bits_per_pixel;
		var->blue.offset	= 0;
		var->transp.length	= 0;
	}

	return 0;
}

static void gpufb_set_var(struct fb_var_screeninfo *var)
{
	/* Initialize VAR */
	var->nonstd        = 0;
	var->activate      = FB_ACTIVATE_NOW;
	var->accel_flags   = 0;
	var->vmode         = FB_VMODE_NONINTERLACED;
	
	var->xres          = GET_THD(par->gpu_regs);
	var->xres_virtual  = GET_THD(par->gpu_regs);
	var->yres          = GET_TVD(par->gpu_regs);
	var->yres_virtual  = GET_TVD(par->gpu_regs);
	var->bits_per_pixel= BPP_16;

  /*
	  Thd = xres
	  Tvd = yres
	  Ths = left margin + xres + right margin + hsync len
	  Thp = hsync len
	  The = hsync len + left margin
	  Tvs = upper margin + yres + lower margin + vsync len
	  Tvp = vsync len
	  Tve = vsync len + upper margin
  */
	var->hsync_len     = GET_THP(par->gpu_regs);
	var->vsync_len     = GET_TVP(par->gpu_regs);
	var->left_margin	 = GET_THE(par->gpu_regs) - var->hsync_len;
	var->upper_margin  = GET_TVE(par->gpu_regs) - var->vsync_len;
	var->lower_margin  = GET_TVS(par->gpu_regs) - var->upper_margin \
                                              - var->yres \
                                              - var->vsync_len;
	var->right_margin  = GET_THS(par->gpu_regs) - var->left_margin \
                                              - var->xres \
                                              - var->hsync_len;
  var->height        = var->yres;
	var->width         = var->xres;
	var->red.offset    = 11;
	var->green.offset  = 5;
	var->blue.offset   = 0;
	var->transp.offset = 0;
	var->red.length    = 5;
	var->green.length  = 6;
	var->blue.length   = 5;
	var->transp.length = 0;
}

static int gpufb_set_par(struct fb_info *info)
{
	struct gpufb_par *fbi = info->par;
	struct fb_var_screeninfo *var = &info->var;

	dprintk("set_par(var=%p, info=%p)\n", var, info);
	
	if (var->bits_per_pixel == 16)
		fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
	else
		fbi->fb->fix.visual = FB_VISUAL_PSEUDOCOLOR;

	fbi->fb->fix.line_length = (var->width*var->bits_per_pixel)/8;
	
	/* Activate this new configuration */
  /*
	  Thd = xres
	  Tvd = yres
	  Ths = left margin + xres + right margin + hsync len
	  Thp = hsync len
	  The = hsync len + left margin
	  Tvs = upper margin + yres + lower margin + vsync len
	  Tvp = vsync len
	  Tve = vsync len + upper margin
  */
	SET_LCDV(fbi->gpu_regs, var->upper_margin + var->yres + var->lower_margin + var->vsync_len,
	                        var->vsync_len,
	                        var->vsync_len + var->upper_margin);
	SET_LCDH(fbi->gpu_regs, var->left_margin + var->xres + var->right_margin + var->hsync_len,
	                        var->hsync_len,
	                        var->hsync_len + var->left_margin);
	SET_LCDR(fbi->gpu_regs, var->yres, var->xres);

	return 0;
}

static int gpufb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
	struct gpufb_par *par = info->par;
	void __user *argp = (void __user *)arg;
	u32 reg_val, glt_word[4];
	struct graphic_layer_table glt_reg;
	
	flush_cache_all();
	
	switch (cmd) {
		case FBIOCTL_SET_CTRL: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_CTL(par->gpu_regs, reg_val);
			return 0;
		}
		
		case FBIOCTL_GET_CTRL: {
			reg_val = GET_CTL(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_CMD: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_CMD(par->gpu_regs, reg_val);
			return 0;
		}
		
		case FBIOCTL_GET_CMD: {
			reg_val = GET_CMD(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_LCDV: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_LCDV(par->gpu_regs, reg_val >> 20, reg_val >> 10, reg_val);
			gpufb_set_var(&par->fb->var);
			return 0;
		}
		
		case FBIOCTL_GET_LCDV: {
			reg_val = GET_LCDV(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_LCDH: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_LCDH(par->gpu_regs, reg_val >> 20, reg_val >> 10, reg_val);
			gpufb_set_var(&par->fb->var);
			return 0;
		}
		
		case FBIOCTL_GET_LCDH: {
			reg_val = GET_LCDH(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_LCDRES: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_LCDR(par->gpu_regs, reg_val >> 10, reg_val);
			gpufb_set_var(&par->fb->var);
			return 0;
		}
		
		case FBIOCTL_GET_LCDRES: {
			reg_val = GET_LCDR(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_STATUS: {
			reg_val = GET_STAT(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_GLTIDX: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			down(&glt_sem);
			SET_GLTIDX(par->gpu_regs, reg_val);
			up(&glt_sem);
			return 0;
		}
		
		case FBIOCTL_GET_GLTIDX: {
			down(&glt_sem);
			reg_val = GET_GLTIDX(par->gpu_regs);
			up(&glt_sem);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_GLT: {
			if(copy_from_user(&glt_reg, argp, sizeof(struct graphic_layer_table)))
				return -EFAULT;
			down(&glt_sem);
			SET_GLT(glt_reg, par->gpu_regs);
			up(&glt_sem);
			return 0;
		}
		
		case FBIOCTL_GET_GLT: {
			down(&glt_sem);
			GET_GLT(glt_word, par->gpu_regs);
			up(&glt_sem);
			return copy_to_user(argp, glt_word, 
			                    sizeof(struct graphic_layer_table)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_DISP_ADDR: {
			reg_val = GET_DISP_PAGE(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_BUF_ADDR: {
			reg_val = GET_DISP_PAGE(par->gpu_regs);
			if(reg_val == fb_base0)
				reg_val = fb_base1;
			else
				reg_val = fb_base0;
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_TPRATE: {
			reg_val = GET_TP_RATE(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_TPRATE: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_TP(par->gpu_regs, reg_val, GET_TP_VALUE(par->gpu_regs));
			break;
		}
		
		case FBIOCTL_GET_TPVALUE: {
			reg_val = GET_TP_VALUE(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_TPVALUE: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_TP(par->gpu_regs, GET_TP_RATE(par->gpu_regs), reg_val);
			break;
		}
		
		case FBIOCTL_GET_FBBASE0: {
			reg_val = GET_FB_BASE0(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_FBBASE0: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_FB_BASE0(par->gpu_regs, reg_val);
			break;
		}
		
		case FBIOCTL_GET_FBBASE1: {
			reg_val = GET_FB_BASE1(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_FBBASE1: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_FB_BASE1(par->gpu_regs, reg_val);
			break;
		}
		
		case FBIOCTL_GET_FBVGA2BASE: {
			reg_val = GET_FB_VGA2_BASE(par->gpu_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_FBVGA2BASE: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_FB_VGA2_BASE(par->gpu_regs, reg_val);
			break;
		}
		
		case FBIOCTL_GPU_WAIT: {
			if(wait_event_interruptible(wq, gpu_free))
				return -ERESTARTSYS;
			break;
		}
		
		/* Video-in ioctls */
		case FBIOCTL_GET_VIBASE: {
			reg_val = GET_VI_BASE(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_VIBASE: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_VI_BASE(par->vi_regs, reg_val);
			break;
		}
		
		case FBIOCTL_GET_VI_HEIGHT: {
			reg_val = GET_VI_HEIGHT(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_VI_WIDTH: {
			reg_val = GET_VI_WIDTH(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_VI_FRMSZ: { /* reg_val[1] = height, reg_val[0] = width */
			if(copy_from_user(&reg_val, argp, sizeof(u16) * 2))
				return -EFAULT;
			SET_VI_FRM_SZ(par->vi_regs, ((u16 *)&reg_val)[0], ((u16 *)&reg_val)[1]);
			break;
		}
		
		case FBIOCTL_GET_VI_CTRL: {
			reg_val = GET_VI_CTRL(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_VI_CTRL: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_VI_CTRL(par->vi_regs, reg_val);
			break;
		}
		
		case FBIOCTL_GET_VI_LINE: {
			reg_val = GET_VI_LINES(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_GET_VI_PIXEL: {
			reg_val = GET_VI_PIXELS(par->vi_regs);
			return copy_to_user(argp, &reg_val, sizeof(reg_val)) ? -EFAULT : 0;
		}
		
		case FBIOCTL_SET_VI_SMTB: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_VI_SYNC_TIMING_BL(par->vi_regs, reg_val);
			break;
		}
		
		case FBIOCTL_SET_VI_SMTD: {
			if(copy_from_user(&reg_val, argp, sizeof(reg_val)))
				return -EFAULT;
			SET_VI_SYNC_TIMING_DT(par->vi_regs, reg_val);
			break;
		}
		
		default:
			return -EINVAL;
	}
	
	flush_cache_all();
	
	return 0;
}


static int gpufb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
	dprintk("gpufb_mmap called\n");
	/* this is uClinux (no MMU) specific code */
	vma->vm_flags |= (VM_RESERVED | VM_MAYSHARE);
	vma->vm_start = (unsigned) fb_base0;
	return 0;
}


static struct fb_ops gpufb_ops = {
	.owner          = THIS_MODULE,
	.fb_check_var   = gpufb_check_var,
	.fb_set_par     = gpufb_set_par,
	.fb_setcolreg   = NULL,
	.fb_pan_display = NULL,
	.fb_blank       = NULL,
	.fb_fillrect    = cfb_fillrect,
	.fb_copyarea    = cfb_copyarea,
	.fb_imageblit   = cfb_imageblit,
	.fb_read        = NULL,
	.fb_write       = NULL,
	.fb_ioctl       = gpufb_ioctl,
	.fb_mmap        = gpufb_mmap,
};

static void show_logo(u8 *scr)
{
	memset(scr, 0x0, GPU_FB_SIZE);
}

static void gpufb_init_register(struct gpufb_par *par)
{
	struct graphic_layer_table init_glt;
	
	/* Initialize GPU */
	SET_CTL(par->gpu_regs, GPU_REG_CONTROL_LCD_EN | \
	                       GPU_REG_CONTROL_GPU_EN | \
	                       GPU_REG_CONTROL_MENU_EN | \
//	                       GPU_REG_CONTROL_INT_EN | \
	                       GPU_REG_CONTROL_VIDEO_EN);
	
	if(fb_base0)
		SET_FB_BASE0(par->gpu_regs, fb_base0);
	else dprintk("Framebuffer base 0 is not set properly\n");
	
	if(fb_base1)
		SET_FB_BASE1(par->gpu_regs, fb_base1);
	else dprintk("Framebuffer base 1 is not set properly\n");
		
	SET_TP(par->gpu_regs, M100V0, 0);
	/* Initialize GLT */
	dprintk("Initialize Graphic Layer Table\n");
	memset(&init_glt, 0, sizeof(struct graphic_layer_table));
	SET_GLT(init_glt, par->gpu_regs);
	
	/* Initialize Video-in */
	if(vi_fb_base) {
		SET_VI_BASE(par->vi_regs, vi_fb_base);
		SET_FB_VGA2_BASE(par->gpu_regs, vi_fb_base);
	}
	else dprintk("VideoIn framebuffer base is not set properly\n");
		
	SET_VI_CTRL(par->vi_regs, VI_REG_CONTROL_EN | VI_REG_CONTROL_INV_DCLK);
	SET_VI_FRM_SZ(par->vi_regs, 640, 512);
}

static int __init gpufb_probe(struct platform_device *pdev)
{
	struct fb_info *fbinfo;
	int err;
	
	fbinfo = framebuffer_alloc(sizeof(struct gpufb_par), &pdev->dev);
	if (!fbinfo) {
		return -ENOMEM;
	}
	
	par = fbinfo->par;
	par->fb = fbinfo;

	if(!request_region((unsigned long)GPU_REG_BASE, GPU_REG_LEN, "GPU Register")) {
		err = -EBUSY;
		goto ERR_GPU_REGION;
	}
	dprintk("Got GPU Register\n");

	if(!request_region((unsigned long)VIDEOIN_REG_BASE, VIDEOIN_REG_LEN, "Video In")) {
		err = -EBUSY;
		goto ERR_VI_REGION;
	}
	dprintk("Got VideoIn Register\n");
	
	if(!request_region((unsigned long)VIDEOIN_FB_BASE, VIDEOIN_FB_SIZE, "Video In Framebuffer")) {
		err = -EBUSY;
		goto ERR_VIFB_REGION;
	}
	dprintk("Got VideoIn Framebuffer\n");
	
	/* Remap the register and buffer space */
	par->gpu_regs = (struct gpufb_reg __iomem *)ioremap((unsigned long)GPU_REG_BASE, GPU_REG_LEN);
	par->vi_regs = (struct videoin_reg __iomem *)ioremap((unsigned long)VIDEOIN_REG_BASE, VIDEOIN_REG_LEN);
	
	fb_base0 = (unsigned long)__get_free_pages(GFP_KERNEL, GPU_FB_ORDER);
	if(!fb_base0) {
		printk(KERN_ERR "Failed to allocate frame buffer, size=%x\n", GPU_FB_ORDER);
		err = -ENOMEM;
		goto ERR_FB_NOMEM;
	}
	fb_base1 = fb_base0 + GPU_FB_PGSIZE;
	printk("GPU Framebuffer #1 base address: 0x%x\n", (unsigned int)fb_base0);
	printk("GPU Framebuffer #2 base address: 0x%x\n", (unsigned int)fb_base1);
	
	vi_fb_base = (unsigned long)ioremap((unsigned long)VIDEOIN_FB_BASE, VIDEOIN_FB_SIZE);
	if(!vi_fb_base) {
		printk(KERN_ERR "Failed to remap video in frame buffer, size=%x\n", VIDEOIN_FB_SIZE);
		err = -ENOMEM;
		goto ERR_FB_NOMEM;
	}
	printk("VideoIn Framebuffer base address: 0x%x\n", (unsigned int)vi_fb_base);
	
	if (register_framebuffer(fbinfo) < 0) {
		printk(KERN_ERR "Failed to register framebuffer device\n");
		err = -EINVAL;
		goto ERR_REG_FB;
	}
	dprintk(KERN_INFO "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id);
	
	/* Init fbinfo structure */
	fbinfo->screen_base       = (u8 *)fb_base0;
	fbinfo->fix.type          = FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.type_aux      = 0; 
	fbinfo->fix.xpanstep      = 0;
	fbinfo->fix.ypanstep      = 0;
	fbinfo->fix.accel         = FB_ACCEL_NONE;
	//fbinfo->fix.mmio_start    = fb_base0;
	//fbinfo->fix.mmio_len      = GPU_FB_SIZE;
	fbinfo->fix.smem_start    = fb_base0;
	fbinfo->fix.smem_len      = GPU_FB_SIZE; //GET_THD(par->gpu_regs) * GET_TVD(par->gpu_regs) * BPP_16 / 8;
	fbinfo->fbops             = &gpufb_ops;
	fbinfo->flags             = FBINFO_FLAG_DEFAULT;
	
	gpufb_set_var(&fbinfo->var);
	gpufb_set_par(fbinfo);
	
	/* Init GPU Registers */
	gpufb_init_register(par);
	
  fbinfo->fix.line_length   = (fbinfo->var.width * fbinfo->var.bits_per_pixel)/8;

	gpufb_check_var(&fbinfo->var, fbinfo);
	
	platform_set_drvdata(pdev, fbinfo);
	
	show_logo(fbinfo->screen_base);
	
	return 0;

ERR_REG_FB:
	free_pages(fb_base0, GPU_FB_ORDER);
ERR_FB_NOMEM:
	iounmap((void *)par->gpu_regs);
	iounmap((void *)par->vi_regs);
	release_mem_region((unsigned long)VIDEOIN_FB_BASE, VIDEOIN_FB_SIZE);
ERR_VIFB_REGION:
	release_mem_region((unsigned long)VIDEOIN_REG_BASE, VIDEOIN_REG_LEN);
ERR_VI_REGION:
	release_mem_region((unsigned long)GPU_REG_BASE, GPU_REG_LEN);
ERR_GPU_REGION:
	framebuffer_release(fbinfo);
	return err;
}

static int gpufb_remove(struct platform_device *pdev)
{
	struct fb_info *fbinfo = platform_get_drvdata(pdev);
	
	unregister_framebuffer(fbinfo);
	free_pages(fb_base0, GPU_FB_ORDER);
	iounmap(GPU_REG_BASE);
	iounmap(VIDEOIN_REG_BASE);
	release_mem_region((unsigned long)GPU_REG_BASE, GPU_REG_LEN);
	release_mem_region((unsigned long)VIDEOIN_REG_BASE, VIDEOIN_REG_LEN);
	framebuffer_release(fbinfo);

	return 0;
}

static int gpufb_suspend(struct platform_device *pdev, pm_message_t state)
{
	return 0;
}

static int gpufb_resume(struct platform_device *dev)
{
	return 0;
}

static struct platform_driver gpufb_driver = {
	.probe = gpufb_probe,
	.remove = gpufb_remove,
	.suspend = gpufb_suspend,
	.resume	= gpufb_resume,
	.driver	= {
		.name = "gpufb",
		.owner = THIS_MODULE,
	},
};

static struct resource gpu_lcd_resource[] = {
	[0] = {
		.start = (u32)GPU_REG_BASE,
		.end   = (u32)(GPU_REG_BASE + GPU_REG_LEN - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = (u32)VIDEOIN_REG_BASE,
		.end   = (u32)(VIDEOIN_REG_BASE + VIDEOIN_REG_LEN - 1),
		.flags = IORESOURCE_MEM,
	},
};

static struct platform_device gpufb_device = {
	.name = "gpufb",
	.id = -1,
	.num_resources = ARRAY_SIZE(gpu_lcd_resource),
	.resource = gpu_lcd_resource,
};

irqreturn_t gpu_interrupt(int irq, void *dev_id)
{
	u32 reg_val;
	
	if(par)
		reg_val = GET_STAT(par->gpu_regs);
	else
		dprintk("PAR is not set\n");
	
	gpu_free = 1;
	wake_up_interruptible(&wq);

	return IRQ_HANDLED;
}

static int __init gpufb_init(void)
{
	int ret;
	ret = platform_driver_register(&gpufb_driver);
	
	if (!ret) {
		ret = platform_device_register(&gpufb_device);
		if (ret)
			platform_driver_unregister(&gpufb_driver);
	}
	
	if((ret = request_irq(GPU_IRQ, gpu_interrupt, SA_INTERRUPT, "GPU", NULL)) < 0) {
		  dprintk("Can't get assigned irq\n");
		  platform_device_unregister(&gpufb_device);
			platform_driver_unregister(&gpufb_driver);
		  return ret;
	}
	dprintk("Interrupt installed on IRQ %d\n", GPU_IRQ);
	init_MUTEX(&glt_sem);
	return ret;
}

static void __exit gpufb_cleanup(void)
{
	free_irq(GPU_IRQ, NULL);
	platform_device_unregister(&gpufb_device);
	platform_driver_unregister(&gpufb_driver);
}


module_init(gpufb_init);
module_exit(gpufb_cleanup);

MODULE_AUTHOR("Kenny Chen <kenny_chen@powerdevote.com.tw>");
MODULE_DESCRIPTION("Framebuffer driver for the NIOS2 Dashboard System");
MODULE_LICENSE("GPL");
